/*
 * Copyright (C) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "task_worker_napi.h"
#include "log.h"

namespace ImageKnifePro {
bool TaskWorkerNapi::QueueWork(InnerWorker *worker)
{
    IMAGE_KNIFE_CHECK_RETURN(worker == nullptr, false);
    napi_status status;

    napi_value taskInfo;
    status = napi_create_string_utf8(env_, ("IMAGE_KNIFE_C " + worker->taskInfo).c_str(), NAPI_AUTO_LENGTH, &taskInfo);
    if (status != napi_ok) {
        IMAGE_KNIFE_LOG(LOG_ERROR, "%{public}s create resource name failed, napi status:%{public}d (Queue Work)",
                        worker->taskInfo.c_str(), status);
        delete worker;
        return false;
    }

    status = napi_create_async_work(env_, nullptr, taskInfo, InnerWorker::ExecuteCallback,
                                    InnerWorker::CompleteCallback, worker, &worker->work);
    if (status != napi_ok) {
        IMAGE_KNIFE_LOG(LOG_ERROR, "%{public}s create async work failed, napi status:%{public}d (Queue Work)",
                        worker->taskInfo.c_str(), status);
        delete worker;
        return false;
    }

    status = napi_queue_async_work_with_qos(env_, worker->work, worker->qos);
    if (status != napi_ok) {
        napi_delete_async_work(env_, worker->work);
        IMAGE_KNIFE_LOG(LOG_ERROR, "%{public}s queue async work failed, napi status:%{public}d (Queue Work)",
                        worker->taskInfo.c_str(), status);
        delete worker;
        return false;
    }

    return true;
}

// static
void TaskWorkerNapi::CallJsCallback(napi_env env, napi_value jsCallback, void *context, void* usrdata)
{
    IMAGE_KNIFE_CHECK(context == nullptr);
    IMAGE_KNIFE_CHECK(usrdata == nullptr);
    InnerWorker *worker = static_cast<InnerWorker *>(usrdata);
    if (worker->mainThreadFunc != nullptr) {
        // 抛回主线程的函数在该环境下执行，并在执行完毕后析构worker
        worker->mainThreadFunc(worker->data);
        delete worker;
        return;
    }

    static_cast<TaskWorkerNapi*>(context)->QueueWork(worker);
}

bool TaskWorkerNapi::QueueWorkThreadsafe(InnerWorker *worker)
{
    IMAGE_KNIFE_CHECK_RETURN(worker == nullptr, false);
    if (threadsafeFunc_ == nullptr) {
        IMAGE_KNIFE_LOG(LOG_ERROR, "%{public}s Threadsafe func not ready (Queue Work Threadsafe)",
                        worker->taskInfo.c_str());
        delete worker;
        return false;
    }

    napi_status status = napi_acquire_threadsafe_function(threadsafeFunc_);
    if (status != napi_ok) {
        IMAGE_KNIFE_LOG(LOG_ERROR,
                        "%{public}s Acquire threadsafe func Failed, napi status:%{public}d (Queue Work Threadsafe)",
                        worker->taskInfo.c_str(), status);
        delete worker;
        return false;
    }

    status = napi_call_threadsafe_function(threadsafeFunc_, worker, napi_tsfn_blocking);
    if (status != napi_ok) {
        IMAGE_KNIFE_LOG(LOG_ERROR,
                        "%{public}s Call threadsafe function Failed, napi status:%{public}d (Queue Work Threadsafe)",
                        worker->taskInfo.c_str(), status);
        delete worker;
        return false;
    }
    return true;
}

void TaskWorkerNapi::ToMainThread(std::function<void(void*)> func, void* data)
{
    IMAGE_KNIFE_CHECK(func == nullptr);
    InnerWorker *worker = new InnerWorker;
    worker->mainThreadFunc = func;
    worker->data = data;
    QueueWorkThreadsafe(worker);
}

bool TaskWorkerNapi::PushTask(AsyncFunc execute, AsyncFunc complete, void *data,
                              std::string taskInfo, ImageKnifeQOS qos)
{
    if (env_ == nullptr) {
        IMAGE_KNIFE_LOG(LOG_ERROR, "%{public}s napi_env is null (Push Task)", taskInfo.c_str());
    }

    InnerWorker *worker = new InnerWorker;
    worker->execute = execute;
    worker->complete = complete;
    worker->data = data;
    worker->qos = static_cast<napi_qos_t>(qos);
    worker->taskInfo = taskInfo;

    if (IsMainThread()) {
        return QueueWork(worker);
    } else {
        return QueueWorkThreadsafe(worker);
    }
}

bool TaskWorkerNapi::PushTask(AsyncTask execute, AsyncTask complete,
                              std::shared_ptr<ImageKnifeTaskInternal> task, ImageKnifeQOS qos)
{
    if (env_ == nullptr) {
        task->EchoError("napi_env is null (Push Task)");
    }

    InnerWorker *worker = new InnerWorker;
    worker->task = task;
    worker->executeTask = execute;
    worker->completeTask = complete;
    worker->qos = static_cast<napi_qos_t>(qos);
    worker->taskInfo = ImageKnifeTaskInternal::GetTaskInfo(task->GetImageRequestType(),
                                                           task->GetImageKnifeRequestInternal());

    if (IsMainThread()) {
        return QueueWork(worker);
    } else {
        return QueueWorkThreadsafe(worker);
    }
}

void TaskWorkerNapi::PrepareThreadsafeFunc()
{
    napi_status status;
    napi_value workName;
    status = napi_create_string_utf8(env_, "ImageKnife Napi Thread Safe Worker", NAPI_AUTO_LENGTH, &workName);
    if (status != napi_ok) {
        IMAGE_KNIFE_LOG(LOG_ERROR, "PrepareThreadsafeFunc: Create work string failed, errorCode:%{public}d", status);
        return;
    }

    status = napi_create_threadsafe_function(env_, nullptr, nullptr, workName, 0, 1,
                                             nullptr, nullptr, this, CallJsCallback, &threadsafeFunc_);
    if (status != napi_ok) {
        IMAGE_KNIFE_LOG(LOG_ERROR, "PrepareThreadsafeFunc: Create Thread Safe function Failed, errorCode:%{public}d",
                        status);
    }
}

void TaskWorkerNapi::InitNapiEnv()
{
    PrepareThreadsafeFunc();
}

} // end of namespace

